EEG - Electrode Placement
Difficulty Level:
Tags record☁eeg☁electrode placement☁snr

The acquisition of Electroencephalogram (EEG) data requires the correct electrode placement for good signal quality. For conventional EEG data acquisitions, electrodes are placed according to the 10-20 system to measure the electric potentials from the skin.

The potential differences are measured between each electrode and a reference electrode whereas in bipolar recording such as the one existent on PLUX"s sensors, the potential difference is measured between adjacent electrodes.
Hence when using a single PLUX EEG sensor with two electrodes, one needs to decide on one specific electrode position of the 10-20 system relating to the desired brain region for data acquisition.

This Jupyter Notebook illustrates the electrode placement for acquiring EEG signals when the researcher wants to extract the alpha band frequencies for the task of closing the eyes and compares the signal quality by measuring the Signal-to-noise ratio (SNR) .

For more information on alpha band extraction as well as SNR please refer to the notebooks: EEG - Alpha band extraction and EEG - Digital filtering .

1 - Importation of the needed packages

In [1]:
# Biosignalsnotebooks python package
import biosignalsnotebooks as bsnb

# Scientific packages
from numpy import logical_and, trapz
from scipy.signal import welch

2 - Electrode Placement
2.1 - Preparation
Consider the following steps before placing the electrodes at the desired region:

  1. Check if the electrode has been used before - if so take a new one to ensure that the glue works well
  2. Adjust the pre-gelled electrodes to the sensor before fixing them into the scalp
  3. Remove hair to the sides so that the electrode is glued very well into the scalp and try to have as little hair between the electrode as possible
  4. Support the fixation of electrodes with the provided headband to ensure sufficient contact with the scalp

2.2 - Alpha Band Frequency location
For measuring the alpha band frequencies (8-12 Hz), the electrodes are placed on the occipital (O) / parietal (P) positions due to formation of alpha oscillations in the occipital lobe.
The neutral electrode is placed behind the ear (M1) position.
For more information on alpha waves, please refer to the notebook EEG - Alpha band extraction .

2.3 - Setup
The following setup of electrode placements were tested in order to compare the signal quality for measurement of alpha band power.

List of electrode placements used for alpha power detection:

  1. "Occipital / Parietal lobe"
    1. "O1 / O2 position" [3 cm]
    2. "P3 / P4 position" [3 cm]
  2. "Occipital - horizontal / vertical alignment"
    1. "O1/O2 - h" [0 / 1 / 2 cm]
    2. "O1 - v" [0 / 1 / 2 cm]
    </ol>

1.A 1.B 1.B
2.A
2.B

3 - Loading of acquired EEG data and Unit Conversion
For a detailed explanation on how to load the acquired EEG data as well as performing the Unit Conversion of the raw data, please refer to the notebooks Load acquired data from .txt file and EEG-Unit Conversion

A - Electrode Position O1/O2

In [2]:
# Load Data from signal samples library - electrode position O1/O2:
data, header = bsnb.load_signal("eeg_sample_o", get_header=True)

# [The acquired EEG data is at channel 1 ("CH1")]
eeg_data = data["CH1"]

B - P3/P4 Segment

In [3]:
#Load Data from signal samples library 
#A: P3/P4 Segment:
data_eeg_p = bsnb.load_signal("eeg_sample_p")

# [The acquired EEG data is at channel 1 ("CH1")]
#A: P3/P4 Segment:
eeg_data_p = data_eeg_p["CH1"]

C - O1/O2 Horizontal Segment

In [4]:
#B:O1/O2 horizontal Segment:
data_eeg_0cm_hor = bsnb.load_signal("eeg_sample_0cm_h")
data_eeg_1cm_hor = bsnb.load_signal("eeg_sample_1cm_h")
data_eeg_2cm_hor = bsnb.load_signal("eeg_sample_2cm_h")

# [The acquired EEG data is at channel 1 ("CH1")]
#B:O1/O2 horizontal Segment:
eeg_data_0cm_hor = data_eeg_0cm_hor["CH1"]
eeg_data_1cm_hor = data_eeg_1cm_hor["CH1"]
eeg_data_2cm_hor = data_eeg_2cm_hor["CH1"]

D - O1/O2 Vertical Segment

In [5]:
#C:O1 vertical Segment:
data_eeg_0cm_ver = bsnb.load_signal("eeg_sample_0cm_v")
data_eeg_1cm_ver = bsnb.load_signal("eeg_sample_1cm_v")
data_eeg_2cm_ver = bsnb.load_signal("eeg_sample_2cm_v")

# [The acquired EEG data is at channel 1 ("CH2")]
#C:O1 vertical Segment:
eeg_data_0cm_ver = data_eeg_0cm_ver["CH2"]
eeg_data_1cm_ver = data_eeg_1cm_ver["CH2"]
eeg_data_2cm_ver = data_eeg_2cm_ver["CH2"]

3.1 - Store acquisition constants
Two extremely relevant parameters defined before data acquisition are the sampling rate and ADC resolution. On one of the previous cells (substep A ) we only request the header of eeg_sample_o signal sample because the remaining signal samples were collected in similar conditions (at the same sampling rate and resolution).

In [6]:
sr = header["sampling rate"] # Sampling Rate
resolution = header["resolution"][0]

3.2 - Conversion of ADC sample values to physical units (uV) and generation of a time-axis
The device used to acquire EEG data belongs to the "biosignalsplux" model. In order to convert the raw EEG signal to its physical units, the recorded data must be passed as an input of raw_to_phy function.

A - Electrode Position O1/O2

In [7]:
#Unit Conversion #electrode position O1/O2:
eeg_signal_uv = bsnb.raw_to_phy("EEG", "biosignalsplux", eeg_data, resolution, "uV")
time_eeg_signal_uv = bsnb.generate_time(eeg_signal_uv, sr)

B - P3/P4 Segment

In [8]:
#Unit Conversion
#A: P3/P4 Segment:
eeg_signal_uv_p = bsnb.raw_to_phy("EEG", "biosignalsplux", eeg_data_p, resolution, "uV")
time_eeg_signal_uv_p = bsnb.generate_time(eeg_signal_uv_p, sr)

C - O1/O2 Horizontal Segment

In [9]:
#B:O1/O2 horizontal Segment:
eeg_signal_uv_0cm_hor = bsnb.raw_to_phy("EEG", "biosignalsplux", eeg_data_0cm_hor, resolution, "uV")
time_eeg_signal_uv_0cm_hor = bsnb.generate_time(eeg_signal_uv_0cm_hor, sr)

eeg_signal_uv_1cm_hor = bsnb.raw_to_phy("EEG", "biosignalsplux", eeg_data_1cm_hor, resolution, "uV")
time_eeg_signal_uv_1cm_hor = bsnb.generate_time(eeg_signal_uv_1cm_hor, sr)

eeg_signal_uv_2cm_hor = bsnb.raw_to_phy("EEG", "biosignalsplux", eeg_data_2cm_hor, resolution, "uV")
time_eeg_signal_uv_2cm_hor = bsnb.generate_time(eeg_signal_uv_2cm_hor, sr)

D - O1/O2 Vertical Segment

In [10]:
#C:O1 vertical Segment:
eeg_signal_uv_0cm_ver = bsnb.raw_to_phy("EEG", "biosignalsplux", eeg_data_0cm_ver, resolution, "uV")
time_eeg_signal_uv_0cm_ver = bsnb.generate_time(eeg_signal_uv_0cm_ver, sr)

eeg_signal_uv_1cm_ver = bsnb.raw_to_phy("EEG", "biosignalsplux", eeg_data_1cm_ver, resolution, "uV")
time_eeg_signal_uv_1cm_ver = bsnb.generate_time(eeg_signal_uv_1cm_ver, sr)

eeg_signal_uv_2cm_ver = bsnb.raw_to_phy("EEG", "biosignalsplux", eeg_data_2cm_ver, resolution, "uV")
time_eeg_signal_uv_2cm_ver = bsnb.generate_time(eeg_signal_uv_2cm_ver, sr)

4 - Signal Quality Check - Effect of Electrode placement
4.1 - Raw Data
The figure shows the raw eeg data of electrode positions: O1/O2 and P3/P4:

In [11]:
#Plot of Raw data - O1/O2 and P3/P4 electrode position:
bsnb.plot([time_eeg_signal_uv , time_eeg_signal_uv_p], [eeg_signal_uv, eeg_signal_uv_p], y_axis_label = "[uV]",x_axis_label = "Time (sec)",title=['RAW EEG DATA: O1/O2 - 3 cm','P3/P4 - 3 cm'], x_range=(40,50),y_range=(-30,30), grid_lines=1, grid_columns=2,grid_plot=True)

The following plot illustrates the raw eeg data of O1 vertical and O1/O2 horizontal electrode placement with each 0 / 1 / 2 cm distance:

In [12]:
#Plot of Raw data - O1 vertical and O1/O2 horizontal electrode positions
bsnb.plot([time_eeg_signal_uv_0cm_ver, time_eeg_signal_uv_0cm_hor], [eeg_signal_uv_0cm_ver, eeg_signal_uv_0cm_hor], y_axis_label = "[uV]",x_axis_label = "Time (sec)",title=['RAW EEG DATA: O1 vertical 0 cm','O1/O2 horizontal 0 cm'], x_range=(50,60),y_range=(-30,30), grid_lines=1, grid_columns=2,grid_plot=True)
bsnb.plot([time_eeg_signal_uv_1cm_ver, time_eeg_signal_uv_1cm_hor], [eeg_signal_uv_1cm_ver, eeg_signal_uv_1cm_hor], y_axis_label = "[uV]",x_axis_label = "Time (sec)",title=['RAW EEG DATA: O1 vertical 0 cm','O1/O2 horizontal 0 cm'], x_range=(50,60),y_range=(-30,30), grid_lines=1, grid_columns=2,grid_plot=True)
bsnb.plot([time_eeg_signal_uv_2cm_ver, time_eeg_signal_uv_2cm_hor], [eeg_signal_uv_2cm_ver, eeg_signal_uv_2cm_hor], y_axis_label = "[uV]",x_axis_label = "Time (sec)",title=['RAW EEG DATA: O1 vertical 0 cm','O1/O2 horizontal 0 cm'], x_range=(50,60),y_range=(-30,30), grid_lines=1, grid_columns=2,grid_plot=True)

4.2 - Power Spectrum - SNR
The signal-to-noise ratio (SNR) is compared as a measure of signal quality.
For further explanation on how to extract the band power from the acquired signal please refer to the notebook EEG - Alpha band extraction and EEG - Digital filtering to read more on noise reduction.

In this case there is no filter applied !

In [13]:
# Time window of closed eyes
t_closed_start = 0 # lower limit of time window (s)
sample_closed_start = t_closed_start*sr

t_closed_end = 60 # Upper limit of time window (s)
sample_closed_end = t_closed_end*sr

4.2.1 - Generation of Power Spectrum by Fast Fourier Transform (FFT) and Welchs Method

A - Electrode Position O1/O2

In [14]:
#Time Windows for Welchs method 
win = 4 * sr # 4 seconds time windows.

#FFT with time windows using scipy.signal.welch
freq_axis, power_spect = welch(eeg_signal_uv[sample_closed_start:sample_closed_end], sr, nperseg=win)

B - P3/P4 Segment

In [15]:
#FFT with time windows using scipy.signal.welch
#A: P3/P4 Segment:
freq_axis_p, power_spect_p = welch(eeg_signal_uv_p[sample_closed_start:sample_closed_end], sr, nperseg=win)

C - O1/O2 Horizontal Segment

In [16]:
#FFT with time windows using scipy.signal.welch
#B:O1/O2 horizontal Segment:
freq_axis_0cm_hor, power_spect_0cm_hor = welch(eeg_signal_uv_0cm_hor[sample_closed_start:sample_closed_end], sr, nperseg=win)
freq_axis_1cm_hor, power_spect_1cm_hor = welch(eeg_signal_uv_1cm_hor[sample_closed_start:sample_closed_end], sr, nperseg=win)
freq_axis_2cm_hor, power_spect_2cm_hor = welch(eeg_signal_uv_2cm_hor[sample_closed_start:sample_closed_end], sr, nperseg=win)

D - O1/O2 Vertical Segment

In [17]:
#FFT with time windows using scipy.signal.welch
#C:O1 vertical Segment:
freq_axis_0cm_ver, power_spect_0cm_ver = welch(eeg_signal_uv_0cm_ver[sample_closed_start:sample_closed_end], sr, nperseg=win)
freq_axis_1cm_ver, power_spect_1cm_ver = welch(eeg_signal_uv_1cm_ver[sample_closed_start:sample_closed_end], sr, nperseg=win)
freq_axis_2cm_ver, power_spect_2cm_ver = welch(eeg_signal_uv_2cm_ver[sample_closed_start:sample_closed_end], sr, nperseg=win)

Defining the Alpha Frequency Band:

In [18]:
#Define Frequency Band limits:
freq_low_cutoff = 8 #lower limit for alpha band
freq_high_cutoff = 12 #Upper limit for alpha band 

A - Electrode Position O1/O2

In [19]:
#Find the intersection Values of the alpha band in the frequency vector
idx_alpha = logical_and(freq_axis >= freq_low_cutoff, freq_axis <= freq_high_cutoff)

B - P3/P4 Segment

In [20]:
#Find the intersection Values of the alpha band in the frequency vector
#A: P3/P4 Segment:
idx_alpha_p = logical_and(freq_axis_p >= freq_low_cutoff, freq_axis_p <= freq_high_cutoff)

C - O1/O2 Horizontal Segment

In [21]:
#Find the intersection Values of the alpha band in the frequency vector
#B:O1/O2 horizontal Segment:
idx_alpha_0cm_hor = logical_and(freq_axis_0cm_hor >= freq_low_cutoff, freq_axis_0cm_hor <= freq_high_cutoff)
idx_alpha_1cm_hor = logical_and(freq_axis_1cm_hor >= freq_low_cutoff, freq_axis_1cm_hor <= freq_high_cutoff)
idx_alpha_2cm_hor = logical_and(freq_axis_2cm_hor >= freq_low_cutoff, freq_axis_2cm_hor <= freq_high_cutoff)

D - O1/O2 Vertical Segment

In [22]:
#Find the intersection Values of the alpha band in the frequency vector
#C:O1 vertical Segment:
idx_alpha_0cm_ver = logical_and(freq_axis_0cm_ver >= freq_low_cutoff, freq_axis_0cm_ver <= freq_high_cutoff)
idx_alpha_1cm_ver = logical_and(freq_axis_1cm_ver >= freq_low_cutoff, freq_axis_1cm_ver <= freq_high_cutoff)
idx_alpha_2cm_ver = logical_and(freq_axis_2cm_ver >= freq_low_cutoff, freq_axis_2cm_ver <= freq_high_cutoff)

4.2.2 -Extraction of Alpha and Total Band Power from the Power Spectrum

A - Electrode Position O1/O2

In [23]:
#Frequency Resolution
freq_res = freq_axis[1] - freq_axis[0] 

#Compute the Absolute Power with numpy.trapz:
alpha_power = trapz(power_spect[idx_alpha],dx=freq_res)
total_power = trapz(power_spect,dx=freq_res)

B - P3/P4 Segment

In [24]:
#A: P3/P4 Segment:
#Frequency Resolution
freq_res_p = freq_axis_p[1] - freq_axis_p[0] 

#Compute the Absolute Power with numpy.trapz:
alpha_power_p = trapz(power_spect_p[idx_alpha_p],dx=freq_res_p)
total_power_p = trapz(power_spect_p,dx=freq_res_p)

C - O1/O2 Horizontal Segment

In [25]:
#B:O1/O2 horizontal Segment:
#Frequency Resolution
freq_res_0cm_hor = freq_axis_0cm_hor[1] - freq_axis_0cm_hor[0] 
freq_res_1cm_hor = freq_axis_1cm_hor[1] - freq_axis_1cm_hor[0]
freq_res_2cm_hor = freq_axis_2cm_hor[1] - freq_axis_2cm_hor[0]

#Compute the Absolute Power with numpy.trapz:
alpha_power_0cm_hor = trapz(power_spect_0cm_hor[idx_alpha_0cm_hor],dx=freq_res_0cm_hor)
total_power_0cm_hor = trapz(power_spect_0cm_hor, dx = freq_res_0cm_hor)

alpha_power_1cm_hor = trapz(power_spect_1cm_hor[idx_alpha_1cm_hor],dx=freq_res_1cm_hor)
total_power_1cm_hor = trapz(power_spect_1cm_hor, dx = freq_res_1cm_hor)

alpha_power_2cm_hor = trapz(power_spect_2cm_hor[idx_alpha_2cm_hor],dx=freq_res_2cm_hor)
total_power_2cm_hor = trapz(power_spect_2cm_hor, dx = freq_res_2cm_hor)

D - O1/O2 Vertical Segment

In [26]:
#C:O1 vertical Segment:
#Frequency Resolution
freq_res_0cm_ver = freq_axis_0cm_ver[1] - freq_axis_0cm_ver[0] 
freq_res_1cm_ver = freq_axis_1cm_ver[1] - freq_axis_1cm_ver[0] 
freq_res_2cm_ver = freq_axis_2cm_ver[1] - freq_axis_2cm_ver[0] 

#Compute the Absolute Power with numpy.trapz:
alpha_power_0cm_ver = trapz(power_spect_0cm_ver[idx_alpha_0cm_ver],dx=freq_res_0cm_ver)
total_power_0cm_ver = trapz(power_spect_0cm_ver, dx = freq_res_0cm_ver)

alpha_power_1cm_ver = trapz(power_spect_1cm_ver[idx_alpha_1cm_ver],dx=freq_res_1cm_ver)
total_power_1cm_ver = trapz(power_spect_1cm_ver, dx = freq_res_1cm_ver)

alpha_power_2cm_ver = trapz(power_spect_2cm_ver[idx_alpha_2cm_ver],dx=freq_res_2cm_ver)
total_power_2cm_ver = trapz(power_spect_2cm_ver, dx = freq_res_2cm_ver)

4.2.3 Calculation of Signal-to-Noise ratio for alpha band power:
The Signal-to-Noise ratio is applied as a measure of signal quality. It is defined as the ratio of signal power to noise power where a high SNR value refers to a signal with low noise and a low SNR value refers to a signal with high noise.

$SNR(f) = \frac{Signal Power (f)}{Noise Power (f)}$

A - Electrode Position O1/O2

In [27]:
#Calculating the SNR:
snr = alpha_power / total_power 

B - P3/P4 Segment

In [28]:
#A: P3/P4 Segment:
snr_p = alpha_power_p / total_power_p 

C - O1/O2 Horizontal Segment

In [29]:
#B:O1/O2 horizontal Segment:
snr_0cm_hor = alpha_power_0cm_hor / total_power_0cm_hor 
snr_1cm_hor = alpha_power_1cm_hor / total_power_1cm_hor 
snr_2cm_hor = alpha_power_2cm_hor / total_power_2cm_hor 

D - O1/O2 Vertical Segment

In [30]:
#C:O1 vertical Segment:
snr_0cm_ver = alpha_power_0cm_ver / total_power_0cm_ver 
snr_1cm_ver = alpha_power_1cm_ver / total_power_1cm_ver 
snr_2cm_ver = alpha_power_2cm_ver / total_power_2cm_ver
In [31]:
from sty import fg, rs

print('1. Occipital / Parietal:')
print('A: O1/O2:',fg(98,195,238) +  "\033[1mSNR: \033[0m" + fg.rs + '[3 cm]:', str(round(snr,2)))
print('B: P3/P4:',fg(98,195,238) +  "\033[1mSNR: \033[0m" + fg.rs + '[3 cm]:', str(round(snr_p,2)))

print('')
print('2. Occipital - horizontal / vertical:')
print('A: O1/O2 - h:',fg(98,195,238) +  "\033[1mSNR: \033[0m" + fg.rs + '[0 cm]:', str(round(snr_0cm_hor,2)), '', ' [1 cm]:',str(round(snr_1cm_hor,2)), '', '[2 cm]:',str(round(snr_2cm_hor,2)), '')
print('B:    O1 - v:',fg(98,195,238) +  "\033[1mSNR: \033[0m" + fg.rs + '[0 cm]:', str(round(snr_0cm_ver,2)), '', ' [1 cm]:',str(round(snr_1cm_ver,2)), '', '[2 cm]:',str(round(snr_2cm_ver,2)), '')
1. Occipital / Parietal:
A: O1/O2: SNR: [3 cm]: 0.49
B: P3/P4: SNR: [3 cm]: 0.67

2. Occipital - horizontal / vertical:
A: O1/O2 - h: SNR: [0 cm]: 0.16   [1 cm]: 0.25  [2 cm]: 0.25 
B:    O1 - v: SNR: [0 cm]: 0.15   [1 cm]: 0.29  [2 cm]: 0.16 

For measuring the alpha band power in the posterior part of the brain, higher Signal-to-noise ratio was calculated with a value of 0.67 for an EEG signal acquisition in the parietal lobe (P3/P4) compared to a value of 0.49 measured in the occipital lobe (O1/O2) using 3 cm distance between the electrodes. For distances below 3 cm low SNR values with values between 0.15 - 0.29 were calculated for both horizontal as well as vertical electrode positioning.

This Jupyter Notebook showed how different electrode positions can affect the quality of the EEG signal as well as how to measure the quality calculating the SNR. Based on the measured SNR values the optimal electrode position for the detection of alpha waves could be defined as P3/P4 in the parietal lobe.

However, it is important to consider that despite the same experimental conditions for each electrode position the calculated SNRs might vary due to surrounding noise which could not be removed or different electrode stability on the scalp. To read more on artefacts influencing the signal refer to the notebook Digital Filtering - EEG .

We hope that you have enjoyed this guide. biosignalsnotebooks is an environment in continuous expansion, so don"t stop your journey and learn more with the remaining Notebooks !

In [32]:
from biosignalsnotebooks.__notebook_support__ import css_style_apply
css_style_apply()
.................... CSS Style Applied to Jupyter Notebook .........................
Out[32]: